package solver;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;

import model.Problem;
import model.Process;
import model.Solution;
import model.SolverEnvironment;

public class SelectionAgent {
	Problem problem;
	Solution current_solution;
	Map<Integer, HashSet<Integer>> taboo[];
	Random random;
	int TABOO_LIMIT = 50000;
	SolverEnvironment env;

	public SelectionAgent(SolverEnvironment env) {
		this.env = env;
		this.problem = env.getProblem();
		this.random = env.getRandom();
		this.current_solution = env.getCurrentSolution();
		taboo = new Map[problem.P];
		for (int i = 0; i < problem.P; i++) {
			taboo[i] = new HashMap<Integer, HashSet<Integer>>();
		}
	}

	public void setSolution(Solution sol) {
		this.current_solution = sol;
	}

	// a simple bubble sort for starters
	protected void sort(long[] cc, int[] m_a) {
		for (int i = 1; i < cc.length; i++) {
			for (int j = cc.length - 1; j >= i; j--) {
				if (cc[j - 1] < cc[j]) {
					long temp1 = cc[j];
					cc[j] = cc[j - 1];
					cc[j - 1] = temp1;
					int temp2 = m_a[j];
					m_a[j] = m_a[j - 1];
					m_a[j - 1] = temp2;
				}
			}
		}
	}

	public List<Integer> selectProcesses(int heavyMachinesSize,
			int processesBelongingToHeavyLoadedMachines,
			int processesBelongingToLightLoadedMachines) {
		List<Integer> selectedProcesses = new ArrayList<Integer>();
		current_solution.refreshLoadCostContribution();
		current_solution.refreshBalanceCostContribution();
		int[] m_a = new int[problem.M];
		long[] cc = new long[problem.M];

		for (int i = 0; i < problem.M; i++) {
			m_a[i] = i;
			cc[i] = current_solution.balance_cost_contribution_zipped[i]
					+ current_solution.load_cost_contribution_zipped[i];
		}

		// int r = random.nextInt(problem.RE);
		// for (int i = 0; i < problem.M; i++) {
		// m_a[i] = i;
		// cc[i] = current_solution.balance_cost_contribution_unzipped[i][r]
		// + current_solution.load_cost_contribution_unzipped[i][r];
		// }

		sort(cc, m_a);
		selectedProcesses
				.addAll(selectProcessesFromMachines(m_a, 0,
						heavyMachinesSize - 1,
						processesBelongingToHeavyLoadedMachines));
		selectedProcesses.addAll(selectProcessesFromMachines(m_a,
				heavyMachinesSize, problem.M - 1,
				processesBelongingToLightLoadedMachines));
		return selectedProcesses;
	}

	private List<Integer> selectProcessesFromMachines(int[] m_a,
			int start_index, int finish_index, int numberOfProcesses) {
		List<Integer> machines = new ArrayList<Integer>();
		for (int i = start_index; i <= finish_index; i++)
			machines.add(m_a[i]);
		List<Integer> eligibleProcesses = new ArrayList<Integer>();
		for (Integer m_id : machines) {
			eligibleProcesses.addAll(current_solution
					.getProcessesForMachine(m_id));
		}
		Collections.shuffle(eligibleProcesses);
		List<Integer> selectedProcesses = new ArrayList<Integer>();
		for (int i = 0; i < numberOfProcesses; i++)
			selectedProcesses.add(eligibleProcesses.get(i));
		return selectedProcesses;
	}

	public void randomlySelectMachinesForProcesses(
			List<Integer> selectedProcesses, List<Integer> ballot_machines,
			int noem) {
		looseMemory();
		if (ballot_machines.size() < noem)
			noem = ballot_machines.size();
		for (Integer p_id : selectedProcesses) {
			Process proc = problem.getProcesses().get(p_id);
			int current_machine_id = current_solution
					.getMachineIdForProcessId(p_id);
			proc.clearEligibleMachines();
			Collections.shuffle(ballot_machines, random);
			for_loop: for (Integer candidate_machine_id : ballot_machines) {

				// proc.addEligibleMachine(candidate_machine_id);
				// if (proc.getEligibleMachines().size() == noem - 1)
				// break for_loop;

				if ((taboo[p_id].containsKey(current_machine_id))
						&& (!taboo[p_id].get(current_machine_id).contains(
								candidate_machine_id))) {
					proc.addEligibleMachine(candidate_machine_id);
				} else if (!taboo[p_id].containsKey(current_machine_id)) {
					proc.addEligibleMachine(candidate_machine_id);
				}
				if (proc.getEligibleMachines().size() == noem - 1)
					break for_loop;
			}
			if (proc.getEligibleMachines().contains(
					new Integer(current_machine_id)))
				proc.addEligibleMachine(ballot_machines.get(noem - 1));
			else {
				proc.addEligibleMachine(current_machine_id);
			}
			addTabooValues(p_id, current_machine_id, proc.getEligibleMachines());
		}
	}

	private void addTabooValues(Integer p_id, int current_machine_id,
			List<Integer> eligibleMachines) {
		if (!taboo[p_id].containsKey(current_machine_id)) {
			HashSet<Integer> machinesList = new HashSet<Integer>();
			machinesList.addAll(eligibleMachines);
			taboo[p_id].put(current_machine_id, machinesList);
		} else {
			taboo[p_id].get(current_machine_id).addAll(eligibleMachines);
		}
	}

	private void looseMemory() {
		int ts = tabooSize();
		if (Problem.VERBOSE)
			System.out.println("TABOO SIZE = " + ts);
		boolean done = ts < TABOO_LIMIT;
		outer: while (!done) {
			int k = random.nextInt(problem.P);
			for (int i = 0; i < problem.P; i++) {
				int p_id = (i + k) % problem.P;
				for (Integer m_id : taboo[p_id].keySet()) {
					if (random.nextDouble() < 0.1) {
						int N = taboo[p_id].get(m_id).size();
						removeItems(taboo[p_id].get(m_id), N / 2);
						ts -= N / 2;
						if (ts < TABOO_LIMIT / 2) {
							done = true;
							break outer;
						}

					}
				}
			}
		}
		// System.out.println("TABOO SIZE = " + ts + " " + tabooSize());
	}

	private void removeItems(HashSet<Integer> set, int N) {
		List<Integer> aList = new ArrayList<Integer>(set);
		Collections.shuffle(aList, random);
		for (int i = 0; i < N; i++)
			set.remove(aList.get(i));
	}

	private int tabooSize() {
		int c = 0;
		for (int i = 0; i < problem.P; i++) {
			for (int m_id = 0; m_id < problem.M; m_id++) {
				if (taboo[i].containsKey(m_id)) {
					c += taboo[i].get(m_id).size();
				}
			}
		}
		return c;
	}

	public void printTaboo() {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < problem.P; i++) {
			StringBuilder sb2 = new StringBuilder();
			for (int m_id = 0; m_id < problem.M; m_id++) {
				if (taboo[i].containsKey(m_id))
					sb2.append(String.format("%d->%s", m_id, taboo[i].get(m_id)));
			}
			if (sb2.length() > 0)
				sb.append(String.format("p%03d %s\n", i, sb2.toString()));
		}
		System.out.println(sb.toString());
	}

}
